﻿using System;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Graphics;

namespace Babylon.Primitives
{
    /// <summary>
    /// Base class for all ellipses
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public abstract class EllipseBase<T> : IDrawableShape where T : struct, IPositionNormal
    {
        readonly float _verticalRay;
        readonly float _horizontalRay;
        readonly int _subdivisions;


        protected EllipseBase(float verticalRay, float horizontalRay, int verticalSubdivisions)
        {
            if (verticalSubdivisions < 1)
            {
                throw new IndexOutOfRangeException("verticalSubdivisions should be >= 1");
            }
            _verticalRay = verticalRay;
            _horizontalRay = horizontalRay;
            _subdivisions = verticalSubdivisions;
            BoundingBox = new BoundingBox(new Vector3(-horizontalRay, -verticalRay, -horizontalRay), new Vector3(horizontalRay, verticalRay, horizontalRay));
            BoundingSphere = new BoundingSphere(Vector3.Zero, Math.Max(horizontalRay, verticalRay));
        }


        /// <summary>
        /// get or set the device used to load content or draw the shape
        /// </summary>
        public GraphicsDevice Device
        {
            get;
            set;
        }

        /// <summary>
        /// get the bounding box of the shape
        /// </summary>
        public BoundingBox BoundingBox
        {
            get;
            private set;
        }

        /// <summary>
        /// get the bounding sphere of the shape
        /// </summary>
        public BoundingSphere BoundingSphere
        {
            get;
            private set;
        }

        public Toolbox.Effect Effect
        {
            get;
            set;
        }

        protected abstract void BeforeCreatingVertices(float horizontalRay, float verticalRay, int subdivisions);
        protected abstract void AfterCreatingVertex(ref T vertex, float normalizedZRot, float normalizedYRot);
        protected abstract T CreateVertex();
        protected abstract VertexDeclaration GetVertexDeclaration();

        Vector3 GetPosition(float angleZ, float angleY)
        {
            var rotZ = Matrix.CreateRotationZ(-angleZ);
            var rotY = Matrix.CreateRotationY(angleY);
            var afterRotZ = Vector3.Transform(Vector3.Up, rotZ);
            var complete = Vector3.Transform(afterRotZ, rotY);
            return complete * new Vector3(_horizontalRay, _verticalRay, _horizontalRay);
        }

        /// <summary>
        /// Load content (initialize buffers)
        /// </summary>
        public void LoadContent()
        {
            var vertices = new List<T>();
            var indices = new List<ushort>();
            BeforeCreatingVertices(_horizontalRay, _verticalRay, _subdivisions);
            int totalZRotSteps = 2 + _subdivisions;
            int totalYRotSteps = 2 * totalZRotSteps;


            for (int i = 0; i <= totalZRotSteps  ; i++)
            {
                float normalizedI = i / (float)totalZRotSteps;
                float angleI = normalizedI * MathHelper.Pi;
                for (int j = 0; j <= totalYRotSteps ; j++)
                {
                    float normalizedJ = j / (float)totalYRotSteps;
                    var v0 = CreateVertex(); 
                    float angleJ = normalizedJ * MathHelper.Pi*2;
                    v0.Position = GetPosition(angleI, angleJ) ;
                    v0.Normal = Vector3.Normalize(v0.Position);
                    AfterCreatingVertex(ref v0, normalizedI, normalizedJ);
                    vertices.Add(v0);                   
                }
                if (i > 0)
                {
                    for (int firstIndex = vertices.Count - 2 * (totalYRotSteps+1); (firstIndex + totalYRotSteps+2) < vertices.Count; firstIndex++)
                    {
                        indices.Add((ushort)firstIndex);
                        indices.Add((ushort)(firstIndex + 1));
                        indices.Add((ushort)(firstIndex + totalYRotSteps+1));

                        indices.Add((ushort)(firstIndex + totalYRotSteps+1));
                        indices.Add((ushort)(firstIndex + 1));
                        indices.Add((ushort)(firstIndex + totalYRotSteps+2));
                    }
                }
            }



            T[] vertexArray = vertices.ToArray();
            ushort[] indexArray = indices.ToArray();

            _vb = new VertexBuffer(Device, GetVertexDeclaration(), vertexArray.Length, BufferUsage.WriteOnly);
            _ib = new IndexBuffer(Device, IndexElementSize.SixteenBits, indexArray.Length, BufferUsage.WriteOnly);

            _vb.SetData(0, vertexArray, 0, vertexArray.Length, GetVertexDeclaration().VertexStride);
            _ib.SetData(0, indexArray, 0, indexArray.Length);

        }

        VertexBuffer _vb;
        IndexBuffer _ib;

        /// <summary>
        /// Draw the shape using the associated effect
        /// </summary>
        public void Draw()
        {
            Effect.Apply();
            Device.Indices = _ib;
            Device.SetVertexBuffer(_vb);
            Device.RasterizerState = RasterizerState.CullNone;
            Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, _vb.VertexCount, 0, _ib.IndexCount / 3);
        }
    }
}
